package com.analysis.service.impl;

import java.io.File;
import java.math.BigDecimal;
import java.nio.file.Paths;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONArray;
import com.analysis.common.CalculationUtil;
import com.analysis.common.ConfigUtil;
import com.analysis.common.DateUtil;
import com.analysis.common.FileUtil;
import com.analysis.common.PostConnection;
import com.analysis.model.Data;
import com.analysis.model.KLine;
import com.analysis.model.Similarity;
import com.analysis.model.ZigzagMatch;
import com.analysis.service.KLineService;
import com.typesafe.config.ConfigFactory;

@Service
public class KLineServiceImpl implements KLineService {
	

	private String klinePath = ConfigUtil.getString("data.kline");

	private String lineExe = ConfigUtil.getString("lineExe");

	private ZigzagMatcheServiceImpl zigzagMatch = new ZigzagMatcheServiceImpl();

	private DataSetServiceImpl dataSet = new DataSetServiceImpl();

	private ZigzagServiceImpl zigzagService = new ZigzagServiceImpl();

	private StoreService store = StoreService.getInstance();

	public static void main(String[] args) {
		KLineServiceImpl service = new KLineServiceImpl();

		service.DTWAnalyze("1d794", "sh600007", DateUtil.getSqlDate("2012-01-02"), DateUtil.getSqlDate("2012-03-02"));

		// service.getLine("sh600007", DateUtil.getSqlDate("2012-01-02"),
		// DateUtil.getSqlDate("2012-03-02"));
	}
	@Override
	public List<Similarity> analyzeWithoutExe(String setName, String symbol, Date startDate, Date endDate){
		List<Similarity> similarity = new ArrayList<>();
		List<KLine> subKline = getSubKline(symbol, startDate, endDate);
		
		double[] in = getCloses(subKline);
		int len = in.length;
		
		List<String> set;
		if ("true".equalsIgnoreCase(ConfigUtil.getString("specificSet")) && setName != null) {
			set = dataSet.getSpecificDataSet(setName);
		} else {
			set = dataSet.getRandomDataSet();
		}
		
		for (String eachSymbl : set) {
			System.out.println(eachSymbl);
			List<KLine> klines = getKline(eachSymbl);
			ZigzagMatch match = zigzagMatch.match(in, in.length, klineToData(eachSymbl, klines));
			if(match.getStartPos()!=-1){
				Similarity simi = new Similarity();
				List<KLine> eachSubKlines = klines.subList(match.getStartPos(), (int)(match.getStartPos() + match.getScalar()*len)+1);
				
				simi.setKline(eachSubKlines);
				simi.setSymbol(eachSymbl);
				simi.setName(getName(eachSymbl));
				simi.setSimilarity(new BigDecimal(CalculationUtil.toSpecifiedDecimals(match.getSimilarity(), 5) + ""));
				simi.setLines(getLine(eachSubKlines));
				similarity.add(simi);
			}
		}
		
		return similarity.stream().sorted(Comparator.comparing(Similarity::getSimilarity)).limit(20)
				.collect(Collectors.toList());
	}
	

	@Override
	public List<Similarity> analyze(String setName, String symbol, Date startDate, Date endDate) {
		List<Similarity> similarity = new ArrayList<>();

		List<KLine> subKline = getSubKline(symbol, startDate, endDate);
		File input = Paths.get("./kline/input.txt").toFile();
		store.writeToFile(input, CalculationUtil.toDouble(subKline));

		List<String> set;
		if ("true".equalsIgnoreCase(ConfigUtil.getString("specificSet")) && setName != null) {
			set = dataSet.getSpecificDataSet(setName);
		} else {
			set = dataSet.getRandomDataSet();
		}

		store.deleteFile(Paths.get("./kline/data").toFile());
		store.deleteFile(Paths.get("./kline/out.txt").toFile());

		Map<String, List<KLine>> allKline = new HashMap<>();
		set.forEach(each -> {
			List<KLine> klines = getKline(each);
			List<Double> closePrice = CalculationUtil.toDouble(klines);

			allKline.put(each, klines);
			store.writeKlineClose(each, closePrice);
		});

		// run
		runSection();

		File zigzag = Paths.get("./kline/out.txt").toFile();
		List<String> section = store.readFile(zigzag);

		Map<String, List<KLine>> klineSection = splitKlineSection(section);
		for (Entry<String, List<KLine>> entry : klineSection.entrySet()) {
			String key = entry.getKey().split(",")[0];
			System.out.println(key);
			double math = NumberUtils.toDouble(entry.getKey().split(",")[1]);
			List<KLine> value = entry.getValue();

			Similarity simi = new Similarity();
			simi.setKline(value);
			simi.setSymbol(key);
			simi.setName(getName(key));
			simi.setSimilarity(new BigDecimal(CalculationUtil.toSpecifiedDecimals(math, 5) + ""));
			simi.setLines(writeAnGetData(value));
			similarity.add(simi);
		}

		return similarity.stream().sorted(Comparator.comparing(Similarity::getSimilarity)).limit(20)
				.collect(Collectors.toList());
	}
	

	@Override
	public List<Similarity> DTWAnalyze(String setName, String symbol, Date startDate, Date endDate) {
		List<Similarity> similarity = new ArrayList<>();

		List<KLine> subKline = getSubKline(symbol, startDate, endDate);
		double[] targetClose = CalculationUtil.standardize(toDoubleArr(subKline));
		File input = Paths.get("./kline/input.txt").toFile();
		store.writeToFile(input, CalculationUtil.toDouble(subKline));

		List<String> set;
		if ("true".equalsIgnoreCase(ConfigUtil.getString("specificSet")) && setName != null) {
			set = dataSet.getSpecificDataSet(setName);
		} else {
			set = dataSet.getRandomDataSet();
		}

		store.deleteFile(Paths.get("./kline/data").toFile());
		store.deleteFile(Paths.get("./kline/out.txt").toFile());

		Map<String, List<KLine>> allKline = new HashMap<>();
		set.forEach(each -> {
			List<KLine> klines = getKline(each);
			List<Double> closePrice = CalculationUtil.toDouble(klines);

			allKline.put(each, klines);
			store.writeKlineClose(each, closePrice);
		});

		// run
		runSection();

		File zigzag = Paths.get("./kline/out.txt").toFile();
		List<String> section = store.readFile(zigzag);

		section = section.stream().sorted(Comparator.comparing(s -> {
			return NumberUtils.toDouble(s.split(",")[3]);
		})).limit(10).collect(Collectors.toList());

		Map<String, List<KLine>> klineSection = splitKlineSection(section);
		for (Entry<String, List<KLine>> entry : klineSection.entrySet()) {
			String key = entry.getKey().split(",")[0];
			System.out.println(key);
			List<KLine> value = entry.getValue();
			double[] sectionClose = CalculationUtil.standardize(toDoubleArr(value));

			double distance = DTW.dtw(targetClose, sectionClose);

			Similarity simi = new Similarity();
			simi.setKline(value);
			simi.setSymbol(key);
			simi.setName(getName(key));
			simi.setSimilarity(new BigDecimal(CalculationUtil.toSpecifiedDecimals(distance, 5) + ""));
			simi.setLines(writeAnGetData(value));
			similarity.add(simi);
		}

		return similarity.stream().sorted(Comparator.comparing(Similarity::getSimilarity)).limit(20)
				.collect(Collectors.toList());
	}
	
	@Override
	public List<Similarity> DTWAnalyzeWithoutExe(String setName, String symbol, Date startDate, Date endDate){

		List<Similarity> similarity = new ArrayList<>();
		List<KLine> subKline = getSubKline(symbol, startDate, endDate);
		
		double[] in = getCloses(subKline);
		double[] inStandardize = CalculationUtil.standardize(in);
		int len = in.length;
		
		List<String> set;
		if ("true".equalsIgnoreCase(ConfigUtil.getString("specificSet")) && setName != null) {
			set = dataSet.getSpecificDataSet(setName);
		} else {
			set = dataSet.getRandomDataSet();
		}
		
		for (String eachSymbl : set) {
			System.out.println(eachSymbl);
			List<KLine> klines = getKline(eachSymbl);
			ZigzagMatch match = zigzagMatch.match(in, in.length, klineToData(eachSymbl, klines));
			if(match.getStartPos()!=-1){
				Similarity simi = new Similarity();
				List<KLine> eachSubKlines = klines.subList(match.getStartPos(), (int)(match.getStartPos() + match.getScalar()*len)+1);
				double[] sectionCloseStan = CalculationUtil.standardize(getCloses(eachSubKlines));
				double distance = DTW.dtw(inStandardize, sectionCloseStan);
				
				simi.setKline(eachSubKlines);
				simi.setSymbol(eachSymbl);
				simi.setName(getName(eachSymbl));
				simi.setSimilarity(new BigDecimal(CalculationUtil.toSpecifiedDecimals(distance, 5) + ""));
				simi.setLines(getLine(eachSubKlines));
				similarity.add(simi);
			}
		}
		
		return similarity.stream().sorted(Comparator.comparing(Similarity::getSimilarity)).limit(20)
				.collect(Collectors.toList());
	
	}
	
	public Data klineToData(String name, List<KLine> klines) {
		Data data = new Data();
		double[] closePrices = new double[klines.size()];
		for (int i = 0; i < klines.size(); i++) {
			closePrices[i] = klines.get(i).getClose();
		}

		data.data = closePrices;
		data.len = closePrices.length;
		data.name = name;
		return data;
	}

	private Map<String, List<KLine>> splitKlineSection(List<String> sections) {
		Map<String, List<KLine>> klineSection = new HashMap<>();

		for (String section : sections) {
			String[] sub = section.split(",");
			String symbol = sub[0];
			int startIndex = NumberUtils.toInt(sub[1].trim());
			int endIndex = NumberUtils.toInt(sub[2].trim());
			String match = sub[3];

			klineSection.put(symbol.substring(0, 8) + "," + match,
					getKline(symbol.substring(0, 8)).subList(startIndex, endIndex + 1));
		}
		return klineSection;
	}

	private double[] toDoubleArr(List<KLine> klines) {
		double[] arr = new double[klines.size()];
		for (int i = 0; i < klines.size(); i++) {
			arr[i] = klines.get(i).getClose();
		}
		return arr;
	}

	private void runSection() {
		try {
			Process process = Runtime.getRuntime().exec("zigzag.exe");
			process.waitFor(15, TimeUnit.MINUTES);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void runZigzag() {
		try {
			Process process = Runtime.getRuntime().exec("zigzag.exe -zigzag");
			process.waitFor(1, TimeUnit.MINUTES);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public List<String> getLine(List<KLine> subKline){
		if (lineExe.equals("true")) {
			return writeAnGetData(subKline);
		} else {
			double[] in = getCloses(subKline);
			int maxrecursion = (int)(Math.pow((double)in.length / 15, 0.333));
			if (maxrecursion > 5)
				maxrecursion = 5;
			
			int[] result = zigzagService.calZigzag(in,maxrecursion);

			List<String> lines = new ArrayList<>();
			for (int i = 0; i < result.length; i++) {
				if (result[i]==1) {
					lines.add(subKline.get(i).getDay().toString() + "," + subKline.get(i).getClose());
				}
			}
			return lines;
		}
	}
	
	@Override
	public List<String> getLine(String symbol, Date startDate, Date endDate) {
		List<KLine> subKline = getSubKline(symbol, startDate, endDate);
		return getLine(subKline);
	}

	private double[] getCloses(List<KLine> subKline) {
		double[] in = new double[subKline.size()];
		for (int i = 0; i < subKline.size(); i++) {
			in[i] = subKline.get(i).getClose();
		}
		return in;
	}

	private List<String> writeAnGetData(List<KLine> subKline) {
		File input = Paths.get("./kline/input.txt").toFile();
		File output = Paths.get("./kline/zigzag.txt").toFile();

		store.writeToFile(input, CalculationUtil.toDouble(subKline));

		// run
		runZigzag();
		List<String> raw = store.readFile(output);
		List<String> lines = new ArrayList<>();
		for (int i = 0; i < raw.size(); i++) {
			if ("1".equals(raw.get(i))) {
				lines.add(subKline.get(i).getDay().toString() + "," + subKline.get(i).getClose());
			}
		}

		return lines;
	}

	public String getName(String name) {
		List<String> symbolName = dataSet.getSearchData();
		for (String each : symbolName) {
			if (each.contains(name)) {
				return each.split(",")[1];
			}
		}
		return null;
	}

	@Override
	public List<KLine> getKline(String symbol) {
		List<KLine> klines = store.getKline(symbol);
		if (CollectionUtils.isEmpty(klines)) {
			
			if(isFromWeb()){
				klines = getFromWeb(symbol);
			}else{
				String path = String.format(klinePath, symbol.substring(2));
				klines = FileUtil.readArray(path, KLine.class);
			}

			if (CollectionUtils.isNotEmpty(klines)) {
				Date date = DateUtil.getSqlDate("2000-01-01");

				klines = klines.stream().filter(each -> {
					each.setSymbol(symbol);
					return each.getDay().after(date);
				}).collect(Collectors.toList());
				store.saveKline(symbol, new ArrayList<>(klines));
			}
		}
		return klines;
	}
	
	public boolean isFromWeb(){
		return "true".equalsIgnoreCase(ConfigUtil.getString("fromWeb"));
	}

	public List<KLine> getFromWeb(String symbol) {
		String url = ConfigUtil.getString("kline", symbol, ConfigUtil.getString("scale.day"), 10000 + "");
		String content = new PostConnection().doLoopPost(url);
		return JSONArray.parseArray(content, KLine.class);
	}

	public List<KLine> getSubKline(String symbol, Date startDate, Date endDate) {
		return getKline(symbol).stream().filter(each -> {
			Date date = each.getDay();
			return date.compareTo(startDate) >= 0 && date.compareTo(endDate) <= 0;
		}).collect(Collectors.toList());
	}

	public List<String> getSymbols() {
		return FileUtil.readArray(ConfigUtil.getString("data.symbols"), String.class);
	}

	@Override
	public List<String> getLatestSymbols() throws InterruptedException, ExecutionException {
		List<String> result = new ArrayList<>();
		ExecutorService service = Executors.newFixedThreadPool(2);

		Future<List<String>> LSA = service.submit(() -> getSymbolsFromWeb("dayRange.HSA"));
		Future<List<String>> LSB = service.submit(() -> getSymbolsFromWeb("dayRange.HSB"));

		service.shutdown();
		result.addAll(LSA.get());
		result.addAll(LSB.get());

		return result;
	}

	@Override
	public List<String> getLatestHSA() throws InterruptedException, ExecutionException {
		return getSymbolsFromWeb("dayRange.HSA");
	}

	@Override
	public List<String> getLatestHA() throws InterruptedException, ExecutionException {
		return getSymbolsFromWeb("dayRange.HA");
	}

	private List<String> getSymbolsFromWeb(String key) {
		List<String> symbols = new ArrayList<String>();
		for (int i = 1; i < 10000; i++) {

			String url = ConfigUtil.getString(key, i + "");

			String content = new PostConnection().doLoopPost(url);
			if ("null".equalsIgnoreCase(content)) {
				break;
			}

			content = "arr:" + content + "";
			ConfigFactory.parseString(content).getConfigList("arr").forEach(each -> {
				symbols.add(each.getString("symbol") + "," + each.getString("name"));
			});
		}
		return symbols;

	}
}
